//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#ifndef __SAGITTARIUS__UNICODE_CHAR__H_
#define __SAGITTARIUS__UNICODE_CHAR__H_

//== INCLUDES ========================================================================

#include "sagittarius/core/SagittariusCore.hpp"
#include "sagittarius/core/BaseTypes.hpp"
#include <sstream>

namespace Sagittarius
{
    
    class UnicodeChar
    {
        protected:
            typedef uint32 CodepointType;
        public:
            //empty constructor
            UnicodeChar()
                : _codepoint(0x00), _n_bytes(0) {}
            
            //construct from c-style string (char had better be 1 byte wide)
            const char* From_UTF_8_String(const char* start, const char* end_of_buffer)
            {
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds reversed");
                    SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
                #endif
                //clear codepoint
                _codepoint = 0x00;
                //determine number of bytes
                if(!(*start & 0x80))
                {
                    //one byte wide
                    _codepoint = (CodepointType)(*start);
                    _n_bytes = 1;
                }
                else if(!(*start & 0x40))
                {
                    //continuation byte, but no start byte
                    SAGITTARIUS_THROW(SanityCheckException, "Continuation byte without start byte", "UnicodeChar::From_UTF_8_String");
                }
                else if(!(*start & 0x20))
                {
                    //start of two-byte sequence
                    if(*start == 0xC0 || *start == 0xC1)
                        //overlong sequence
                        SAGITTARIUS_THROW(SanityCheckException, "Overlong sequence", "UnicodeChar::From_UTF_8_String");
                    //get first byte
                    _codepoint = (CodepointType)(*start & 0x1F);
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //second byte:
                    if((*start & 0xC0) != 0xC0)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get second byte
                    _codepoint |= (CodepointType)(*start & 0x3F) << 5;
                    _n_bytes = 2;
                }
                else if(!(*start & 0x10))
                {
                    //start of three-byte sequence
                    //get first byte
                    _codepoint = (CodepointType)(*start & 0x0F) << 12;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //second byte:
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get second byte
                    _codepoint |= (CodepointType)(*start & 0x3F) << 6;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //third byte:
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get third byte
                    _codepoint |= (CodepointType)(*start & 0x3F);
                    _n_bytes = 3;
                }
                else if(!(*start & 0x08))
                {
                    //start of four-byte sequence
                    //get first byte
                    _codepoint = (CodepointType)(*start & 0x07) << 18;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //second byte:
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get second byte
                    _codepoint |= (CodepointType)(*start & 0x3F) << 12;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //third byte:
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get third byte
                    _codepoint |= (CodepointType)(*start & 0x3F) << 6;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //fourth byte:
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get fourth byte
                    _codepoint |= (CodepointType)(*start & 0x3F);
                    if(_codepoint > 0x10FFFF)
                        SAGITTARIUS_THROW(SanityCheckException, "Codepoint exceeds 0x10FFFF (not allowed by current Unicode standard)", "UnicodeChar::From_UTF_8_String");
                    _n_bytes = 4;
                }
                else
                {
                    SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "UnicodeChar::From_UTF_8_String");
                }
                ++start;
                return start;
            }
            
            //construct from c-style string (char had better be 1 byte wide)
            //uses loops instead of conditional blocks
            const char* From_UTF_8_String_Looped(const char* start, const char* end_of_buffer)
            {
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds reversed");
                    SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
                #endif
                //clear codepoint
                _codepoint = 0x00;
                int n = 1;
                if(!(*start & 0x80))
                {
                    //one byte
                    _codepoint = *start;
                    ++start;
                    _n_bytes = 1;
                    return start;
                }
                else
                {
                    for(n = 2; n<=4; ++n)
                    {
                        if(!((*start << n) & 0x80))
                        {
                            break;
                        }
                    }
                }
                if((*start << n) & 0x80)
                    SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "UnicodeChar::From_UTF_8_String");
                _n_bytes = n;
                
                //get data
                //the one-byte case was dealt with above, so we may assume more than one byte
                int marker;
                switch(n)
                {
                    case 2:
                        marker = 6;
                        break;
                    case 3:
                        marker = 12;
                        break;
                    case 4:
                        marker = 18;
                        break;
                }
                _codepoint |= (CodepointType)(*start & (0xFF >> (n+1))) << marker;
                marker -= 6;
                ++start;
                for(int i=1; i<n; ++i)
                {
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    //continuation byte
                    if((*start & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get data
                    _codepoint |= (CodepointType)(*start & 0x3F) << marker;
                    ++start;
                }
                return start;
            }
            
            //construct from c-style string (char had better be 1 byte wide)
            void From_UTF_8_Stream(std::istream& is)
            {
                /*_codepoint = is.get();
                _n_bytes = 1;
                return;*/
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
                #endif
                //clear codepoint
                _codepoint = 0x00;
                char chr;
                is.get(chr);
                //determine number of bytes
                if(!(chr & 0x80))
                {
                    //one byte wide
                    _codepoint = (CodepointType)(chr);
                    _n_bytes = 1;
                }
                else if(!(chr & 0x40))
                {
                    //continuation byte, but no start byte
                    SAGITTARIUS_THROW(SanityCheckException, "Continuation byte without start byte", "UnicodeChar::From_UTF_8_Stream");
                }
                else if(!(chr & 0x20))
                {
                    //start of two-byte sequence
                    if(chr == 0xC0 || chr == 0xC1)
                        //overlong sequence
                        SAGITTARIUS_THROW(SanityCheckException, "Overlong sequence", "UnicodeChar::From_UTF_8_Stream");
                    //get first byte
                    _codepoint = (CodepointType)(chr & 0x1F);
                    is.get(chr);
                    //second byte:
                    if((chr & 0xC0) != 0xC0)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get second byte
                    _codepoint |= (CodepointType)(chr & 0x3F) << 5;
                    _n_bytes = 2;
                }
                else if(!(chr & 0x10))
                {
                    //start of three-byte sequence
                    //get first byte
                    _codepoint = (CodepointType)(chr & 0x0F) << 12;
                    is.get(chr);
                    //second byte:
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get second byte
                    _codepoint |= (CodepointType)(chr & 0x3F) << 6;
                    is.get(chr);
                    //third byte:
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get third byte
                    _codepoint |= (CodepointType)(chr & 0x3F);
                    _n_bytes = 3;
                }
                else if(!(chr & 0x08))
                {
                    //start of four-byte sequence
                    //get first byte
                    _codepoint = (CodepointType)(chr & 0x07) << 18;
                    is.get(chr);
                    //second byte:
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get second byte
                    _codepoint |= (CodepointType)(chr & 0x3F) << 12;
                    is.get(chr);
                    //third byte:
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get third byte
                    _codepoint |= (CodepointType)(chr & 0x3F) << 6;
                    is.get(chr);
                    //fourth byte:
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_Stream");
                    //get fourth byte
                    _codepoint |= (CodepointType)(chr & 0x3F);
                    if(_codepoint > 0x10FFFF)
                        SAGITTARIUS_THROW(SanityCheckException, "Codepoint exceeds 0x10FFFF (not allowed by current Unicode standard)", "UnicodeChar::From_UTF_8_Stream");
                    _n_bytes = 4;
                }
                else
                {
                    SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "UnicodeChar::From_UTF_8_Stream");
                }
                //is.get(chr);
            }
            
            //uses loops instead of conditional blocks
            void From_UTF_8_Stream_Looped(std::istream& is)
            {
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
                #endif
                //clear codepoint
                _codepoint = 0x00;
                int n = 1;
                char chr;
                is.get(chr);
                if(!(chr & 0x80))
                {
                    //one byte
                    _codepoint = chr;
                    //is.get(chr);
                    _n_bytes = 1;
                    return;
                }
                else
                {
                    for(n = 2; n<=4; ++n)
                    {
                        if(!((chr << n) & 0x80))
                        {
                            break;
                        }
                    }
                }
                if((chr << n) & 0x80)
                    SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "UnicodeChar::From_UTF_8_String");
                _n_bytes = n;
                
                //get data
                //the one-byte case was dealt with above, so we may assume more than one byte
                int marker;
                switch(n)
                {
                    case 2:
                        marker = 6;
                        break;
                    case 3:
                        marker = 12;
                        break;
                    case 4:
                        marker = 18;
                        break;
                }
                _codepoint |= (CodepointType)(chr & (0xFF >> (n+1))) << marker;
                marker -= 6;
                for(int i=1; i<n; ++i)
                {
                    is.get(chr);
                    //continuation byte
                    if((chr & 0xC0) != 0x80)
                        SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "UnicodeChar::From_UTF_8_String");
                    //get data
                    _codepoint |= (CodepointType)(chr & 0x3F) << marker;
                }
            }
            
            //get number of bytes comprising character
            int n_bytes() const
            {
                return _n_bytes;
            }
            
            //convert to c-style string
            char* ToC_String(char* start, char* end_of_buffer) const
            {
                if(_n_bytes == 1)
                {
                    *start = (char)(_codepoint & 0x7F);
                }
                else if(_n_bytes == 2)
                {
                    *start = (char)((_codepoint >> 6) & 0x1F) | 0xC0;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)(_codepoint & 0x3F) | 0x80;
                }
                else if(_n_bytes == 3)
                {
                    *start = (char)((_codepoint >> 12) & 0x0F) | 0xE0;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)((_codepoint >> 6) & 0x3F) | 0x80;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)(_codepoint & 0x3F) | 0x80;
                }
                else if(_n_bytes == 4)
                {
                    *start = (char)((_codepoint >> 18) & 0x07) | 0xF0;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)((_codepoint >> 12) & 0x3F) | 0x80;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)((_codepoint >> 6) & 0x3F) | 0x80;
                    ++start;
                    #if SAGITTARIUS_DEBUG_LEVEL >= 1
                        SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                    #endif
                    *start = (char)(_codepoint & 0x3F) | 0x80;
                }
                else
                    SAGITTARIUS_ASSERT(false);
                ++start;
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
                #endif
                return start;
            }
            
            //convert to std::string
            std::string ToStdString() const
            {
                std::string result;
                if(_n_bytes == 1)
                {
                    result += (char)(_codepoint & 0x7F);
                }
                else if(_n_bytes == 2)
                {
                    result += (char)((_codepoint >> 6) & 0x1F) | 0xC0;
                    result += (char)(_codepoint & 0x3F) | 0x80;
                }
                else if(_n_bytes == 3)
                {
                    result += (char)((_codepoint >> 12) & 0x0F) | 0xE0;
                    result += (char)((_codepoint >> 6) & 0x3F) | 0x80;
                    result += (char)(_codepoint & 0x3F) | 0x80;
                }
                else if(_n_bytes == 4)
                {
                    result += (char)((_codepoint >> 18) & 0x07) | 0xF0;
                    result += (char)((_codepoint >> 12) & 0x3F) | 0x80;
                    result += (char)((_codepoint >> 6) & 0x3F) | 0x80;
                    result += (char)(_codepoint & 0x3F) | 0x80;
                }
                else
                {
                    //SAGITTARIUS_ASSERT(false);
                    result = "\\0";
                }
                return result;
            }
            
            //convert to std::string
            std::string ToCodepointString() const
            {
                std::stringstream s;
                s << "U+";
                if(_n_bytes <= 3)
                {
                    //width = 6 bytes
                    s.width(4);
                    s.fill('0');
                    s << std::hex << _codepoint;
                }
                else if(_n_bytes == 4)
                {
                    //width = 8 bytes
                    s.width(6);
                    s.fill('0');
                    s << std::hex << _codepoint;
                }
                else
                    SAGITTARIUS_ASSERT(false);
                return s.rdbuf()->str();
            }
            
            //comparison
            //with another UnicodeChar
            bool operator== (const UnicodeChar& chr) const
            {
                return _codepoint == chr._codepoint;
            }
            //operator!=
            bool operator!= (const UnicodeChar& chr) const
            {
                return !this->operator==(chr);
            }
            //with a C-style char
            bool operator== (const char& byte) const
            {
                #if SAGITTARIUS_DEBUG_LEVEL >= 1
                    SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
                #endif
                //if(_n_bytes == 1) //unnecessary
                    if(_codepoint == byte)
                        return true;
                return false;
            }
            //operator!=
            bool operator!= (const char& byte) const
            {
                return !this->operator==(byte);
            }
            
            //codepoint
            CodepointType _codepoint;
        protected:
            unsigned int _n_bytes;
    };
    
    //stream I/O
    std::ostream& operator<<(std::ostream& os, const UnicodeChar& chr);
    
} //namespace Sagittarius

#endif
